/*
 * LengthUnit.java 22 nov. 2008
 *
 * Sweet Home 3D, Copyright (c) 2006-2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.model;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * Unit used for lengths.
 */
public enum LengthUnit
{
	/**
	 * Millimeter unit.
	 * @since 2.0
	 */
	MILLIMETER {
		private Locale formatLocale;
		private String name;
		private DecimalFormat lengthFormatWithUnit;
		private DecimalFormat lengthFormat;
		private DecimalFormat areaFormatWithUnit;
		
		@Override
		public Format getFormatWithUnit()
		{
			checkLocaleChange();
			return this.lengthFormatWithUnit;
		}
		
		@Override
		public Format getAreaFormatWithUnit()
		{
			checkLocaleChange();
			return this.areaFormatWithUnit;
		}
		
		@Override
		public Format getFormat()
		{
			checkLocaleChange();
			return this.lengthFormat;
		}
		
		@Override
		public String getName()
		{
			checkLocaleChange();
			return this.name;
		}
		
		private void checkLocaleChange()
		{
			// Instantiate formats if locale changed
			if (!Locale.getDefault().equals(this.formatLocale))
			{
				this.formatLocale = Locale.getDefault();
				ResourceBundle resource = ResourceBundle.getBundle(LengthUnit.class.getName());
				this.name = resource.getString("millimeterUnit");
				this.lengthFormatWithUnit = new MeterFamilyFormat("#,##0 " + this.name, 10);
				this.lengthFormat = new MeterFamilyFormat("#,##0", 10);
				String squareMeterUnit = resource.getString("squareMeterUnit");
				this.areaFormatWithUnit = new SquareMeterAreaFormatWithUnit(squareMeterUnit);
			}
		}
		
		@Override
		public float getMagnetizedLength(float length, float maxDelta)
		{
			return getMagnetizedMeterLength(length, maxDelta);
		}
		
		@Override
		public float getMinimumLength()
		{
			return 0.1f;
		}
		
		@Override
		public float getMaximumLength()
		{
			return 100000f;
		}
		
		@Override
		public float centimeterToUnit(float length)
		{
			return length * 10;
		}
		
		@Override
		public float unitToCentimeter(float length)
		{
			return length / 10;
		}
	},
	
	/**
	 * Centimeter unit.
	 */
	CENTIMETER {
		private Locale formatLocale;
		private String name;
		private DecimalFormat lengthFormatWithUnit;
		private DecimalFormat lengthFormat;
		private DecimalFormat areaFormatWithUnit;
		
		@Override
		public Format getFormatWithUnit()
		{
			checkLocaleChange();
			return this.lengthFormatWithUnit;
		}
		
		@Override
		public Format getAreaFormatWithUnit()
		{
			checkLocaleChange();
			return this.areaFormatWithUnit;
		}
		
		@Override
		public Format getFormat()
		{
			checkLocaleChange();
			return this.lengthFormat;
		}
		
		@Override
		public String getName()
		{
			checkLocaleChange();
			return this.name;
		}
		
		private void checkLocaleChange()
		{
			// Instantiate formats if locale changed
			if (!Locale.getDefault().equals(this.formatLocale))
			{
				this.formatLocale = Locale.getDefault();
				ResourceBundle resource = ResourceBundle.getBundle(LengthUnit.class.getName());
				this.name = resource.getString("centimeterUnit");
				this.lengthFormatWithUnit = new MeterFamilyFormat("#,##0.# " + this.name, 1);
				this.lengthFormat = new MeterFamilyFormat("#,##0.#", 1);
				String squareMeterUnit = resource.getString("squareMeterUnit");
				this.areaFormatWithUnit = new SquareMeterAreaFormatWithUnit(squareMeterUnit);
			}
		}
		
		@Override
		public float getMagnetizedLength(float length, float maxDelta)
		{
			return getMagnetizedMeterLength(length, maxDelta);
		}
		
		@Override
		public float getMinimumLength()
		{
			return 0.1f;
		}
		
		@Override
		public float getMaximumLength()
		{
			return 100000f;
		}
		
		@Override
		public float centimeterToUnit(float length)
		{
			return length;
		}
		
		@Override
		public float unitToCentimeter(float length)
		{
			return length;
		}
	},
	
	/**
	 * Meter unit.
	 * @since 2.0
	 */
	METER {
		private Locale formatLocale;
		private String name;
		private DecimalFormat lengthFormatWithUnit;
		private DecimalFormat lengthFormat;
		private DecimalFormat areaFormatWithUnit;
		
		@Override
		public Format getFormatWithUnit()
		{
			checkLocaleChange();
			return this.lengthFormatWithUnit;
		}
		
		@Override
		public Format getAreaFormatWithUnit()
		{
			checkLocaleChange();
			return this.areaFormatWithUnit;
		}
		
		@Override
		public Format getFormat()
		{
			checkLocaleChange();
			return this.lengthFormat;
		}
		
		@Override
		public String getName()
		{
			checkLocaleChange();
			return this.name;
		}
		
		private void checkLocaleChange()
		{
			// Instantiate formats if locale changed
			if (!Locale.getDefault().equals(this.formatLocale))
			{
				this.formatLocale = Locale.getDefault();
				ResourceBundle resource = ResourceBundle.getBundle(LengthUnit.class.getName());
				this.name = resource.getString("meterUnit");
				this.lengthFormatWithUnit = new MeterFamilyFormat("#,##0.00# " + this.name, 0.01f);
				this.lengthFormat = new MeterFamilyFormat("#,##0.00#", 0.01f);
				String squareMeterUnit = resource.getString("squareMeterUnit");
				this.areaFormatWithUnit = new SquareMeterAreaFormatWithUnit(squareMeterUnit);
			}
		}
		
		@Override
		public float getMagnetizedLength(float length, float maxDelta)
		{
			return getMagnetizedMeterLength(length, maxDelta);
		}
		
		@Override
		public float getMinimumLength()
		{
			return 0.1f;
		}
		
		@Override
		public float getMaximumLength()
		{
			return 100000f;
		}
		
		@Override
		public float centimeterToUnit(float length)
		{
			return length / 100;
		}
		
		@Override
		public float unitToCentimeter(float length)
		{
			return length * 100;
		}
	},
	
	/**
	 * Foot/Inch unit followed by fractions.
	 */
	INCH {
		private Locale formatLocale;
		private String name;
		private DecimalFormat lengthFormat;
		private DecimalFormat areaFormatWithUnit;
		
		@Override
		public Format getFormatWithUnit()
		{
			checkLocaleChange();
			return this.lengthFormat;
		}
		
		@Override
		public Format getFormat()
		{
			return getFormatWithUnit();
		}
		
		@Override
		public Format getAreaFormatWithUnit()
		{
			checkLocaleChange();
			return this.areaFormatWithUnit;
		}
		
		@Override
		public String getName()
		{
			checkLocaleChange();
			return this.name;
		}
		
		private void checkLocaleChange()
		{
			// Instantiate format if locale changed
			if (!Locale.getDefault().equals(this.formatLocale))
			{
				this.formatLocale = Locale.getDefault();
				ResourceBundle resource = ResourceBundle.getBundle(LengthUnit.class.getName());
				this.name = resource.getString("inchUnit");
				
				// Create format for feet and inches
				final MessageFormat positiveFootFormat = new MessageFormat(resource.getString("footFormat"));
				final MessageFormat positiveFootInchFormat = new MessageFormat(resource.getString("footInchFormat"));
				final MessageFormat positiveFootInchEighthFormat = new MessageFormat(
						resource.getString("footInchEighthFormat"));
				final MessageFormat negativeFootFormat = new MessageFormat("-" + resource.getString("footFormat"));
				final MessageFormat negativeFootInchFormat = new MessageFormat(
						"-" + resource.getString("footInchFormat"));
				final MessageFormat negativeFootInchEighthFormat = new MessageFormat(
						"-" + resource.getString("footInchEighthFormat"));
				final String footInchSeparator = resource.getString("footInchSeparator");
				final NumberFormat footNumberFormat = NumberFormat.getIntegerInstance();
				final NumberFormat inchNumberFormat = NumberFormat.getNumberInstance();
				final char[] inchFractionCharacters = { '\u215b', // 1/8
						'\u00bc', // 1/4  
						'\u215c', // 3/8
						'\u00bd', // 1/2
						'\u215d', // 5/8
						'\u00be', // 3/4
						'\u215e' }; // 7/8        
				final String[] inchFractionStrings = { "1/8", "1/4", "3/8", "1/2", "5/8", "3/4", "7/8" };
				this.lengthFormat = new DecimalFormat("0.000\"")
				{
					@Override
					public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition)
					{
						float absoluteValue = Math.abs((float) number);
						double feet = Math.floor(centimeterToFoot(absoluteValue));
						float remainingInches = centimeterToInch(
								(float) absoluteValue - footToCentimeter((float) feet));
						if (remainingInches >= 11.9375f)
						{
							feet++;
							remainingInches -= 12;
						}
						fieldPosition.setEndIndex(fieldPosition.getEndIndex() + 1);
						// Format remaining inches only if it's larger that 0.0005
						if (remainingInches >= 0.0005f)
						{
							// Try to format decimals with 1/8, 1/4, 1/2 fractions first
							int integerPart = (int) Math.floor(remainingInches);
							float fractionPart = remainingInches - integerPart;
							int eighth = Math.round(fractionPart * 8);
							if (eighth == 0 || eighth == 8)
							{
								(number >= 0 ? positiveFootInchFormat : negativeFootInchFormat).format(
										new Object[] { feet, Math.round(remainingInches * 8) / 8f }, result,
										fieldPosition);
							}
							else
							{
								(number >= 0 ? positiveFootInchEighthFormat : negativeFootInchEighthFormat).format(
										new Object[] { feet, integerPart, inchFractionCharacters[eighth - 1] }, result,
										fieldPosition);
							}
						}
						else
						{
							(number >= 0 ? positiveFootFormat : negativeFootFormat).format(new Object[] { feet },
									result, fieldPosition);
						}
						return result;
					}
					
					@Override
					public Number parse(String text, ParsePosition parsePosition)
					{
						double value = 0;
						ParsePosition numberPosition = new ParsePosition(parsePosition.getIndex());
						skipWhiteSpaces(text, numberPosition);
						// Parse feet
						int quoteIndex = text.indexOf('\'', parsePosition.getIndex());
						boolean negative = numberPosition.getIndex() < text.length() && text
								.charAt(numberPosition.getIndex()) == this.getDecimalFormatSymbols().getMinusSign();
						if (quoteIndex != -1)
						{
							Number feet = footNumberFormat.parse(text, numberPosition);
							if (feet == null)
							{
								parsePosition.setErrorIndex(numberPosition.getErrorIndex());
								return null;
							}
							skipWhiteSpaces(text, numberPosition);
							if (numberPosition.getIndex() != quoteIndex)
							{
								parsePosition.setErrorIndex(numberPosition.getIndex());
								return null;
							}
							value = footToCentimeter(feet.intValue());
							numberPosition = new ParsePosition(quoteIndex + 1);
							skipWhiteSpaces(text, numberPosition);
							// Test optional foot inch separator
							if (numberPosition.getIndex() < text.length()
									&& footInchSeparator.indexOf(text.charAt(numberPosition.getIndex())) >= 0)
							{
								numberPosition.setIndex(numberPosition.getIndex() + 1);
								skipWhiteSpaces(text, numberPosition);
							}
							if (numberPosition.getIndex() == text.length())
							{
								parsePosition.setIndex(text.length());
								return value;
							}
						}
						// Parse inches
						Number inches = inchNumberFormat.parse(text, numberPosition);
						if (inches == null)
						{
							parsePosition.setErrorIndex(numberPosition.getErrorIndex());
							return null;
						}
						if (negative)
						{
							if (quoteIndex == -1)
							{
								value = inchToCentimeter(inches.floatValue());
							}
							else
							{
								value -= inchToCentimeter(inches.floatValue());
							}
						}
						else
						{
							value += inchToCentimeter(inches.floatValue());
						}
						// Parse fraction
						skipWhiteSpaces(text, numberPosition);
						if (numberPosition.getIndex() == text.length())
						{
							parsePosition.setIndex(text.length());
							return value;
						}
						if (text.charAt(numberPosition.getIndex()) == '\"')
						{
							parsePosition.setIndex(numberPosition.getIndex() + 1);
							return value;
						}
						
						char fractionChar = text.charAt(numberPosition.getIndex());
						String fractionString = text.length() - numberPosition.getIndex() >= 3
								? text.substring(numberPosition.getIndex(), numberPosition.getIndex() + 3) : null;
						for (int i = 0; i < inchFractionCharacters.length; i++)
						{
							if (inchFractionCharacters[i] == fractionChar
									|| inchFractionStrings[i].equals(fractionString))
							{
								// Check no decimal fraction was specified
								int lastDecimalSeparatorIndex = text.lastIndexOf(
										getDecimalFormatSymbols().getDecimalSeparator(), numberPosition.getIndex() - 1);
								if (lastDecimalSeparatorIndex > quoteIndex)
								{
									return null;
								}
								else
								{
									if (negative)
									{
										value -= inchToCentimeter((i + 1) / 8f);
									}
									else
									{
										value += inchToCentimeter((i + 1) / 8f);
									}
									parsePosition.setIndex(numberPosition.getIndex()
											+ (inchFractionCharacters[i] == fractionChar ? 1 : 3));
									skipWhiteSpaces(text, parsePosition);
									if (parsePosition.getIndex() < text.length()
											&& text.charAt(parsePosition.getIndex()) == '\"')
									{
										parsePosition.setIndex(parsePosition.getIndex() + 1);
									}
									return value;
								}
							}
						}
						
						parsePosition.setIndex(numberPosition.getIndex());
						return value;
					}
					
					/**
					 * Increases the index of <code>fieldPosition</code> to skip white spaces. 
					 */
					private void skipWhiteSpaces(String text, ParsePosition fieldPosition)
					{
						while (fieldPosition.getIndex() < text.length()
								&& Character.isWhitespace(text.charAt(fieldPosition.getIndex())))
						{
							fieldPosition.setIndex(fieldPosition.getIndex() + 1);
						}
					}
				};
				
				String squareFootUnit = resource.getString("squareFootUnit");
				this.areaFormatWithUnit = new SquareFootAreaFormatWithUnit("#,##0 " + squareFootUnit);
			}
		}
		
		@Override
		public float getMagnetizedLength(float length, float maxDelta)
		{
			return getMagnetizedInchLength(length, maxDelta);
		}
		
		@Override
		public float getMinimumLength()
		{
			return LengthUnit.inchToCentimeter(0.125f);
		}
		
		@Override
		public float getMaximumLength()
		{
			return LengthUnit.inchToCentimeter(99974.4f); // 3280 ft
		}
		
		@Override
		public float centimeterToUnit(float length)
		{
			return centimeterToInch(length);
		}
		
		@Override
		public float unitToCentimeter(float length)
		{
			return inchToCentimeter(length);
		}
	},
	
	/**
	 * Inch unit with decimals.
	 * @since 4.0
	 */
	INCH_DECIMALS {
		private Locale formatLocale;
		private String name;
		private DecimalFormat lengthFormat;
		private DecimalFormat lengthFormatWithUnit;
		private DecimalFormat areaFormatWithUnit;
		
		@Override
		public Format getFormatWithUnit()
		{
			checkLocaleChange();
			return this.lengthFormatWithUnit;
		}
		
		@Override
		public Format getFormat()
		{
			checkLocaleChange();
			return this.lengthFormat;
		}
		
		@Override
		public Format getAreaFormatWithUnit()
		{
			checkLocaleChange();
			return this.areaFormatWithUnit;
		}
		
		@Override
		public String getName()
		{
			checkLocaleChange();
			return this.name;
		}
		
		private void checkLocaleChange()
		{
			// Instantiate format if locale changed
			if (!Locale.getDefault().equals(this.formatLocale))
			{
				this.formatLocale = Locale.getDefault();
				ResourceBundle resource = ResourceBundle.getBundle(LengthUnit.class.getName());
				this.name = resource.getString("inchUnit");
				
				// Create formats for inches with decimals
				class InchDecimalsFormat extends DecimalFormat
				{
					private final MessageFormat inchDecimalsFormat;
					private final NumberFormat inchNumberFormat = NumberFormat.getNumberInstance();
					
					private InchDecimalsFormat(MessageFormat inchDecimalsFormat)
					{
						super("0.###");
						this.inchDecimalsFormat = inchDecimalsFormat;
					}
					
					@Override
					public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition)
					{
						float inches = centimeterToInch((float) number);
						fieldPosition.setEndIndex(fieldPosition.getEndIndex() + 1);
						this.inchDecimalsFormat.format(new Object[] { inches }, result, fieldPosition);
						return result;
					}
					
					@Override
					public Number parse(String text, ParsePosition parsePosition)
					{
						ParsePosition numberPosition = new ParsePosition(parsePosition.getIndex());
						skipWhiteSpaces(text, numberPosition);
						// Parse inches
						Number inches = this.inchNumberFormat.parse(text, numberPosition);
						if (inches == null)
						{
							parsePosition.setErrorIndex(numberPosition.getErrorIndex());
							return null;
						}
						double value = inchToCentimeter(inches.floatValue());
						// Parse "
						skipWhiteSpaces(text, numberPosition);
						if (numberPosition.getIndex() < text.length() && text.charAt(numberPosition.getIndex()) == '\"')
						{
							parsePosition.setIndex(numberPosition.getIndex() + 1);
						}
						else
						{
							parsePosition.setIndex(numberPosition.getIndex());
						}
						return value;
					}
					
					/**
					 * Increases the index of <code>fieldPosition</code> to skip white spaces. 
					 */
					private void skipWhiteSpaces(String text, ParsePosition fieldPosition)
					{
						while (fieldPosition.getIndex() < text.length()
								&& Character.isWhitespace(text.charAt(fieldPosition.getIndex())))
						{
							fieldPosition.setIndex(fieldPosition.getIndex() + 1);
						}
					}
				}
				this.lengthFormat = new InchDecimalsFormat(new MessageFormat(resource.getString("inchDecimalsFormat")));
				this.lengthFormatWithUnit = new InchDecimalsFormat(
						new MessageFormat(resource.getString("inchDecimalsFormatWithUnit")));
				
				String squareFootUnit = resource.getString("squareFootUnit");
				this.areaFormatWithUnit = new SquareFootAreaFormatWithUnit("#,##0.## " + squareFootUnit);
			}
		}
		
		@Override
		public float getMagnetizedLength(float length, float maxDelta)
		{
			return getMagnetizedInchLength(length, maxDelta);
		}
		
		@Override
		public float getMinimumLength()
		{
			return LengthUnit.inchToCentimeter(0.125f);
		}
		
		@Override
		public float getMaximumLength()
		{
			return LengthUnit.inchToCentimeter(99974.4f); // 3280 ft
		}
		
		@Override
		public float centimeterToUnit(float length)
		{
			return centimeterToInch(length);
		}
		
		@Override
		public float unitToCentimeter(float length)
		{
			return inchToCentimeter(length);
		}
	};
	
	/**
	 * Returns the <code>length</code> given in centimeters converted to inches.
	 */
	public static float centimeterToInch(float length)
	{
		return length / 2.54f;
	}
	
	/**
	 * Returns the <code>length</code> given in centimeters converted to feet.
	 */
	public static float centimeterToFoot(float length)
	{
		return length / 2.54f / 12;
	}
	
	/**
	 * Returns the <code>length</code> given in inches converted to centimeters.
	 */
	public static float inchToCentimeter(float length)
	{
		return length * 2.54f;
	}
	
	/**
	 * Returns the <code>length</code> given in feet converted to centimeters.
	 */
	public static float footToCentimeter(float length)
	{
		return length * 2.54f * 12;
	}
	
	/**
	 * Returns a format able to format lengths with their localized unit.
	 */
	public abstract Format getFormatWithUnit();
	
	/**
	 * Returns a format able to format lengths.
	 */
	public abstract Format getFormat();
	
	/**
	 * A decimal format for meter family units.
	 */
	private static class MeterFamilyFormat extends DecimalFormat
	{
		private final float unitMultiplier;
		
		public MeterFamilyFormat(String pattern, float unitMultiplier)
		{
			super(pattern);
			this.unitMultiplier = unitMultiplier;
			
		}
		
		@Override
		public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition)
		{
			// Convert centimeter to millimeter
			return super.format(number * this.unitMultiplier, result, fieldPosition);
		}
		
		@Override
		public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition)
		{
			return format((double) number, result, fieldPosition);
		}
		
		@Override
		public Number parse(String text, ParsePosition pos)
		{
			Number number = super.parse(text, pos);
			if (number == null)
			{
				return null;
			}
			else
			{
				return number.floatValue() / this.unitMultiplier;
			}
		}
	}
	
	/**
	 * Returns a format able to format areas with their localized unit.
	 */
	public abstract Format getAreaFormatWithUnit();
	
	/**
	 * A decimal format for square meter.
	 */
	private static class SquareMeterAreaFormatWithUnit extends DecimalFormat
	{
		public SquareMeterAreaFormatWithUnit(String squareMeterUnit)
		{
			super("#,##0.## " + squareMeterUnit);
		}
		
		@Override
		public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition)
		{
			// Convert square centimeter to square meter
			return super.format(number / 10000, result, fieldPosition);
		}
	}
	
	/**
	 * A decimal format for square foot.
	 */
	private static class SquareFootAreaFormatWithUnit extends DecimalFormat
	{
		public SquareFootAreaFormatWithUnit(String pattern)
		{
			super(pattern);
		}
		
		@Override
		public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition)
		{
			// Convert square centimeter to square foot
			return super.format(number / 929.0304, result, fieldPosition);
		}
	}
	
	/**
	 * Returns a localized name of this unit.
	 */
	public abstract String getName();
	
	/**
	 * Returns the value close to the given <code>length</code> in centimeter under magnetism. 
	 */
	public abstract float getMagnetizedLength(float length, float maxDelta);
	
	/**
	 * Returns the value close to the given length under magnetism for meter units.
	 */
	private static float getMagnetizedMeterLength(float length, float maxDelta)
	{
		// Use a maximum precision of 1 mm depending on maxDelta
		maxDelta *= 2;
		float precision = 1 / 10f;
		if (maxDelta > 100)
		{
			precision = 100;
		}
		else if (maxDelta > 10)
		{
			precision = 10;
		}
		else if (maxDelta > 5)
		{
			precision = 5;
		}
		else if (maxDelta > 1)
		{
			precision = 1;
		}
		else if (maxDelta > 0.5f)
		{
			precision = 0.5f;
		}
		float magnetizedLength = Math.round(length / precision) * precision;
		if (magnetizedLength == 0 && length > 0)
		{
			return length;
		}
		else
		{
			return magnetizedLength;
		}
	}
	
	/**
	 * Returns the value close to the given length under magnetism for inch units.
	 */
	private static float getMagnetizedInchLength(float length, float maxDelta)
	{
		// Use a maximum precision of 1/8 inch depending on maxDelta
		maxDelta = centimeterToInch(maxDelta) * 2;
		float precision = 1 / 8f;
		if (maxDelta > 6)
		{
			precision = 6;
		}
		else if (maxDelta > 3)
		{
			precision = 3;
		}
		else if (maxDelta > 1)
		{
			precision = 1;
		}
		else if (maxDelta > 0.5f)
		{
			precision = 0.5f;
		}
		else if (maxDelta > 0.25f)
		{
			precision = 0.25f;
		}
		float magnetizedLength = inchToCentimeter(Math.round(centimeterToInch(length) / precision) * precision);
		if (magnetizedLength == 0 && length > 0)
		{
			return length;
		}
		else
		{
			return magnetizedLength;
		}
	}
	
	/**
	 * Returns the minimum value for length in centimeter.
	 */
	public abstract float getMinimumLength();
	
	/**
	 * Returns the maximum value for length in centimeter.
	 * @since 3.4
	 */
	public abstract float getMaximumLength();
	
	/**
	 * Returns the maximum value for elevation in centimeter.
	 * @since 3.4
	 */
	public float getMaximumElevation()
	{
		return getMaximumLength() / 10;
	}
	
	/**
	 * Returns the <code>length</code> given in centimeters converted 
	 * to a value expressed in this unit.
	 * @since 2.0
	 */
	public abstract float centimeterToUnit(float length);
	
	/**
	 * Returns the <code>length</code> given in this unit converted 
	 * to a value expressed in centimeter.
	 * @since 2.0
	 */
	public abstract float unitToCentimeter(float length);
}
